﻿using System;
using System.Runtime.InteropServices;

namespace SpiderMonkey
{
    public sealed class JSVal
    {
        private enum ValueTag : uint
        {
            Clear = 0xFFFF0000,
            Int32 = Clear | 1,
            Undefined = Clear | 2,
            String = Clear | 5,
            Boolean = Clear | 3,
            Magic = Clear | 4,
            Null = Clear | 6,
            Object = Clear | 7
        }
        [StructLayout(LayoutKind.Explicit)]
        private struct JSValLayout
        {
            [FieldOffset(0)]
            public ulong AsBits;
            [FieldOffset(0)]
            public double AsDouble;
            [FieldOffset(0)]
            public IntPtr AsPtr;
            [FieldOffset(0)]
            public int I32;
            [FieldOffset(0)]
            public uint U32;
            [FieldOffset(0)]
            public int Boo;
            [FieldOffset(0)]
            public IntPtr Ptr;
            [FieldOffset(4)]
            public ValueTag Tag;
        }

        private ulong value;

        internal JSVal(ulong value)
        {
            this.value = value;
        }

        public object ToObject()
        {
            if (IsNull() || IsUndefined()) return null;
            if (IsBoolean()) return ValToBoolean();
            if (IsNumber())
            {
                double dp;
                if(Native.JS_ValueToNumber(Context.CurrentContext.Pointer, value, out dp) == 0) throw new SpiderException();
                return dp;
            }
            if (IsInt()) return ValToInt();
            if (IsDouble()) return ValToDouble();
            if (IsString()) return Marshal.PtrToStringAnsi(Native.JS_EncodeString(Context.CurrentContext.Pointer, ValToString()));
            if (IsObject()) return new JSObject(ValToObject(), false);
            throw new SpiderException();
        }
        public override string ToString()
        {
            object obj = ToObject();
            return obj == null ? null : obj.ToString();
        }
        public bool IsNull()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Tag == ValueTag.Null;
        }
        public bool IsUndefined()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Tag == ValueTag.Undefined;
        }
        public bool IsBoolean()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Tag == ValueTag.Boolean;
        }
        public bool IsNumber()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Tag <= ValueTag.Int32;
        }
        public bool IsInt()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Tag == ValueTag.Int32;
        }
        public bool IsDouble()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Tag <= ValueTag.Clear;
        }
        public bool IsString()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Tag == ValueTag.String;
        }
        public bool IsObject()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Tag > ValueTag.Null;
        }

        private bool ValToBoolean()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Boo != 0;
        }
        private int ValToInt()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.I32;
        }
        private double ValToDouble()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.AsDouble;
        }
        private IntPtr ValToString()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Ptr;
        }
        private IntPtr ValToObject()
        {
            JSValLayout layout = new JSValLayout();
            layout.AsBits = value;
            return layout.Ptr;
        }

        public static JSVal CreateString(string str)
        {
            JSValLayout layout = new JSValLayout();
            layout.Tag = ValueTag.String;
            layout.Ptr = Native.JS_NewStringCopyN(Context.CurrentContext.Pointer, str, str.Length);
            return new JSVal(layout.AsBits);
        }
        public static JSVal Create(object o)
        {
            if (o == null) return Null;
            if (o is string) return CreateString((string)o);
            throw new NotImplementedException();
        }

        internal ulong Pointer
        {
            get { return value; }
        }
        public static JSVal Null
        {
            get
            {
                JSValLayout layout = new JSValLayout();
                layout.Tag = ValueTag.Null;
                return new JSVal(layout.AsBits);
            }
        }
        public static JSVal Undefined
        {
            get
            {
                JSValLayout layout = new JSValLayout();
                layout.Tag = ValueTag.Undefined;
                return new JSVal(layout.AsBits);
            }
        }
    }
}
